Media session topic
Show the player in the system media controls and apply the commands they send back.
| Platform | Controls |
|---|---|
| iOS, macOS | Now Playing, Control Center, lock screen |
| Android | Media notification |
| Windows | SMTC |
| Linux | MPRIS |
| Web | navigator.mediaSession |
See the Web topic for the web session.
Session
await player.setMediaSession(const MediaSession()); // title, artist, album and cover from the file
await player.setMediaSession(null); // remove
One Player per process owns the session, and it must live in the root isolate. A second player throws StateError, even from another Flutter engine in the same process. The owner keeps the session until it passes null or is disposed.
Metadata
await player.setMediaSession(
(player.state.mediaSession ?? const MediaSession())
.copyWith(title: 'Custom title', artist: 'Custom artist'),
);
null keeps the file's value, and an empty string leaves the field blank. For a file without tags, each field falls back to title, artist, album and art in Media.extras. With no title anywhere, the session shows the title libmpv takes from the URL.
Artwork
const MediaSession(artwork: MediaSessionArtwork.embedded); // the file's cover, by default
MediaSession(artwork: MediaSessionArtwork.uri(Uri.parse(url))); // an image the package loads
MediaSession(artwork: MediaSessionArtwork.custom(cover)); // your own CoverArt
const MediaSession(artwork: MediaSessionArtwork.none);
On native platforms the package downloads a network cover and hands the OS the bytes, so a URL with credentials never reaches the OS. The download always verifies the certificate and refuses a redirect from https to http. It skips images over 20 MiB.
| Scheme | Where |
|---|---|
http://, https:// |
Every platform |
file:// |
Every platform except the web |
asset:///assets/cover.png |
Every platform, with three slashes |
content://, android.resource:// |
Android, for an image file |
blob: |
The web |
For any other scheme, or a cover that needs headers, load the bytes yourself and use MediaSessionArtwork.custom. A Uri lowercases the host, so pass an Android authority with capitals as a string in extras['art'].
Commands
The player applies OS commands itself and also emits them:
player.stream.mediaSessionCommands.listen((command) {
switch (command) {
case MediaSessionCommandSeekTo(:final position):
print('Scrubbed to $position');
case MediaSessionCommandNext():
myQueue.next(); // with one entry, next is only emitted
case _:
break;
}
});
Actions
actions lists the controls the OS may show. The default is play, pause, play-pause, next, previous and seek. stop, fastForward, rewind, setRepeatMode, setShuffle, setPlaybackRate and like are also available.
const MediaSession(
actions: {
MediaAction.playPause, MediaAction.seek,
MediaAction.fastForward, MediaAction.rewind, MediaAction.next, MediaAction.previous,
},
fastForwardInterval: Duration(seconds: 30), // 15 s by default
autoApplyPlaylistNavigation: false, // next and previous only emit commands
);
The intervals and supportedPlaybackRates apply only with their action in the set. In an audiobook app, for example, map next and previous to a 30 s skip.
A platform leaves out the actions it does not draw. The table shows what the package registers.
| Action | iOS | macOS | Android | Windows | Linux |
|---|---|---|---|---|---|
| play, pause, next, previous | Yes | Yes | Yes | Yes | Yes |
seek |
Yes | Yes | Yes | Yes | Yes |
fastForward, rewind |
Yes | Yes | Yes | Yes | No |
stop |
Yes | Yes | Yes | Yes | Yes |
setRepeatMode, setShuffle |
Yes | Yes | Yes | Yes | Yes |
setPlaybackRate |
Yes | Yes | Yes | Yes | Yes |
like |
Yes | Yes | Yes | No | No |
GNOME shows only previous, play-pause and next.
Interruptions
interruptionPolicy decides what a phone call or another app's audio does on iOS and Android:
const MediaSession(interruptionPolicy: InterruptionPolicy.pauseAndResume); // default
const MediaSession(interruptionPolicy: InterruptionPolicy.pauseOnly); // never resumes on its own
const MediaSession(interruptionPolicy: InterruptionPolicy.keepPlaying);
- With
keepPlaying, an accepted call or a voice assistant still takes the audio on iOS. Android 12 and later may fade the app out. - Unplugging headphones always pauses.
- On iOS the audio does not mix with other apps. Loading a file stops another app's audio, even with
play: false.
Background playback needs the iOS and Android setup in Permissions.
App name on the desktop
const MediaSession(
appName: 'My Player', // MPRIS and SMTC
desktopEntry: 'com.example.myapp', // Linux: the .desktop file name, for the icon
);
macOS, iOS and Android take the name from the app bundle. Windows takes the icon from the executable.
Classes
- MediaSession Media session
- Configuration and metadata for the OS media session: the lockscreen, SMTC or MPRIS entry, Bluetooth AVRCP, headset buttons, and (on iOS and Android) the AVAudioSession or AudioFocus integration.